﻿using System.Security.Cryptography;

namespace FTIOT_CAT1
{
    internal class BinFileAesEncryptor
    {
        // 与原 C++ 程序一致：块大小固定 16，按 1024 字节分块读写
        private const int BlockSize = 16;
        private const int IoChunk = 1024;


        /// <summary>
        /// 读取 HEX（32 个十六进制字符）为 16 字节密钥
        /// </summary>
        private static byte[] ParseHexKey(string hex)
        {
            if (hex == null) throw new ArgumentNullException(nameof(hex));
            if (hex.Length != 32) throw new ArgumentException("HEX 密钥长度必须为 32 个十六进制字符（16 字节）。");
            var buf = new byte[16];
            for (int i = 0; i < 16; i++)
            {
                buf[i] = Convert.ToByte(hex.Substring(i * 2, 2), 16);
            }
            return buf;
        }

        /// <summary>
        /// 与原 C++ jiami() 完全兼容的加密：ECB + 自定义末块填充（0x00... + 最末 1 字节=填充长度）
        /// 仅对最后一块填充，非末块不填充；块内用 AES-128-ECB 逐块处理。&#8203;:contentReference[oaicite:2]{index=2}
        /// </summary>
        public static void EncryptFileEcbCustomPadding(string inputPath, string outputPath, string aesKey)
        {
            byte[] key = ParseHexKey(aesKey);
            if (key.Length != 16) return; // 确保密钥长度为 16 字节
            using var aes = Aes.Create();
            aes.Mode = CipherMode.ECB;          // 与原实现一致（无 IV）
            aes.Padding = PaddingMode.None;     // 自己处理填充
            aes.Key = key;

            using var encryptor = aes.CreateEncryptor();
            using var fin = new FileStream(inputPath, FileMode.Open, FileAccess.Read);
            using var fout = new FileStream(outputPath, FileMode.Create, FileAccess.Write);

            var inBuf = new byte[IoChunk];
            var block = new byte[BlockSize];

            while (true)
            {
                int bytesRead = fin.Read(inBuf, 0, inBuf.Length);
                if (bytesRead == 0)
                {
                    // 输入恰好为 1024 的倍数 -> 这里“按原逻辑”不会再额外添加一块全填充。
                    // 原程序只在读取到 <1024 时（视为最后一批）才做填充。&#8203;:contentReference[oaicite:3]{index=3}
                    break;
                }

                bool isLastChunk = bytesRead < IoChunk;

                // 逐 16 字节处理；最后一块如果非对齐，按自定义规则填充
                int offset = 0;
                while (offset + BlockSize <= bytesRead)
                {
                    TransformBlockECB(encryptor, inBuf, offset, BlockSize, fout);
                    offset += BlockSize;
                }

                int remain = bytesRead - offset;
                if (isLastChunk)
                {
                    // 最后一个分块：如果不是 16 的倍数，需要填充
                    if (remain == 0)
                    {
                        // 恰好对齐：无需填充，直接结束
                        continue;
                    }

                    // 与原 C++ 一致：num = 16 - (remain % 16)
                    // 先把未满的明文字节复制，再把 (num-2) 个字节置 0，最后 1 个字节写入 num。
                    // 注意：原实现 for(i=0;i<num-2;i++) input[len+i]=0; input[len+num-1]=num;（中间的那 1 字节保持原值或上一次残留，效果等同“0填充+尾标尺”）&#8203;:contentReference[oaicite:4]{index=4}
                    int num = BlockSize - (remain % BlockSize);
                    Array.Copy(inBuf, offset, block, 0, remain);

                    // 清 0（安全起见，我们把空洞全部置 0，再把最后一个字节写 num，等价且更稳健）
                    for (int i = remain; i < BlockSize; i++) block[i] = 0x00;
                    block[BlockSize - 1] = (byte)num;

                    TransformBlockECB(encryptor, block, 0, BlockSize, fout);
                }
                else
                {
                    // 中间分块：必须整 16 写入，否则会与原行为不一致（原代码中 1024 是 16 的倍数）
                    if (remain != 0)
                        throw new InvalidOperationException("中间块非 16 对齐（不应发生）。");
                }
            }
        }

        /// <summary>
        /// 与原 C++ jiemi() 完全兼容的解密：ECB + 自定义去填充（仅在“最后一批”按最后 1 字节减去填充长度）。&#8203;:contentReference[oaicite:5]{index=5}
        /// </summary>
        public static void DecryptFileEcbCustomPadding(string inputPath, string outputPath, byte[] key)
        {
            using var aes = Aes.Create();
            aes.Mode = CipherMode.ECB;
            aes.Padding = PaddingMode.None;
            aes.Key = key;

            using var decryptor = aes.CreateDecryptor();
            using var fin = new FileStream(inputPath, FileMode.Open, FileAccess.Read);
            using var fout = new FileStream(outputPath, FileMode.Create, FileAccess.Write);

            var inBuf = new byte[IoChunk];
            var outBuf = new byte[IoChunk];

            while (true)
            {
                int bytesRead = fin.Read(inBuf, 0, inBuf.Length);
                if (bytesRead == 0) break;

                if (bytesRead % BlockSize != 0)
                    throw new InvalidOperationException("密文长度必须为 16 的整数倍。");

                // 整块解密
                int produced = TransformManyBlocksECB(decryptor, inBuf, bytesRead, outBuf);

                bool isLastChunk = bytesRead < IoChunk;

                if (isLastChunk)
                {
                    // 去填充：最后一个字节是填充长度（num），按原程序直接 len -= num。&#8203;:contentReference[oaicite:6]{index=6}
                    if (produced == 0) break;
                    byte pad = outBuf[produced - 1];
                    int padLen = pad;
                    if (padLen <= 0 || padLen > BlockSize) throw new CryptographicException("填充长度非法。");

                    int writeLen = produced - padLen;
                    if (writeLen < 0) throw new CryptographicException("去填充失败。");

                    fout.Write(outBuf, 0, writeLen);
                }
                else
                {
                    fout.Write(outBuf, 0, produced);
                }
            }
        }

        private static int TransformManyBlocksECB(ICryptoTransform t, byte[] inBuf, int inLen, byte[] outBuf)
        {
            int totalOut = 0;
            int offset = 0;
            while (offset < inLen)
            {
                int n = t.TransformBlock(inBuf, offset, BlockSize, outBuf, totalOut);
                totalOut += n;
                offset += BlockSize;
            }
            return totalOut;
        }

        private static void TransformBlockECB(ICryptoTransform t, byte[] buf, int offset, int count, Stream output)
        {
            // ECB 块级转换
            var tmp = new byte[count];
            int n = t.TransformBlock(buf, offset, count, tmp, 0);
            if (n != count) throw new CryptographicException("转换长度不一致。");
            output.Write(tmp, 0, n);
        }
    }
}
